{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Solution Notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problem: Implement an algorithm to have a robot move from the upper left corner to the bottom right corner of a grid.\n",
    "\n",
    "* [Constraints](#Constraints)\n",
    "* [Test Cases](#Test-Cases)\n",
    "* [Algorithm](#Algorithm)\n",
    "* [Code](#Code)\n",
    "* [Unit Test](#Unit-Test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Constraints\n",
    "\n",
    "* Are there restrictions to how the robot moves?\n",
    "    * The robot can only move right and down\n",
    "* Are some cells invalid (off limits)?\n",
    "    * Yes\n",
    "* Can we assume the starting and ending cells are valid cells?\n",
    "    * Yes\n",
    "* Is this a rectangular grid? i.e. the grid is not jagged?\n",
    "    * Yes\n",
    "* Will there always be a valid way for the robot to get to the bottom right?\n",
    "    * No, return None\n",
    "* Can we assume the inputs are valid?\n",
    "    * No\n",
    "* Can we assume this fits memory?\n",
    "    * Yes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test Cases\n",
    "\n",
    "<pre>\n",
    "o = valid cell\n",
    "x = invalid cell\n",
    "\n",
    "   0  1  2  3\n",
    "0  o  o  o  o\n",
    "1  o  x  o  o\n",
    "2  o  o  x  o\n",
    "3  x  o  o  o\n",
    "4  o  o  x  o\n",
    "5  o  o  o  x\n",
    "6  o  x  o  x\n",
    "7  o  x  o  o\n",
    "</pre>\n",
    "\n",
    "* General case\n",
    "\n",
    "```\n",
    "expected = [(0, 0), (1, 0), (2, 0),\n",
    "            (2, 1), (3, 1), (4, 1),\n",
    "            (5, 1), (5, 2), (6, 2), \n",
    "            (7, 2), (7, 3)]\n",
    "```\n",
    "\n",
    "* No valid path, say row 7, col 2 is invalid\n",
    "* None input\n",
    "* Empty matrix"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algorithm\n",
    "\n",
    "To get to row r and column c [r, c], we will need to have gone:\n",
    "\n",
    "* Right from [r, c-1] if this is a valid cell - [Path 1] \n",
    "* Down from [r-1, c] if this is a valid cell - [Path 2]\n",
    "\n",
    "If we look at [Path 1], to get to [r, c-1], we will need to have gone:\n",
    "\n",
    "* Right from [r, c-2] if this is a valid cell\n",
    "* Down from [r-1, c-1] if this is a valid cell\n",
    "\n",
    "Continue this process until we reach the start cell or until we find that there is no path.\n",
    "\n",
    "Base case:\n",
    "\n",
    "* If the input row or col are < 0, or if [row, col] is not a valid cell\n",
    "    * Return False\n",
    "\n",
    "Recursive case:\n",
    "\n",
    "We'll memoize the solution to improve performance.\n",
    "\n",
    "* Use the memo to see if we've already processed the current cell\n",
    "* If any of the following is True, append the current cell to the path and set our result to True:\n",
    "    * We are at the start cell\n",
    "    * We get a True result from a recursive call on:\n",
    "        * [row, col-1]\n",
    "        * [row-1, col]\n",
    "* Update the memo\n",
    "* Return the result\n",
    "\n",
    "Complexity:\n",
    "* Time: O(row * col)\n",
    "* Space: O(row * col) for the recursion depth"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Grid(object):\n",
    "\n",
    "    def find_path(self, matrix):\n",
    "        if matrix is None or not matrix:\n",
    "            return None\n",
    "        cache = {}\n",
    "        path = []\n",
    "        if self._find_path(matrix, len(matrix) - 1, \n",
    "                           len(matrix[0]) - 1, cache, path):\n",
    "            return path\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def _find_path(self, matrix, row, col, cache, path):\n",
    "        if row < 0 or col < 0 or not matrix[row][col]:\n",
    "            return False\n",
    "        cell = (row, col)\n",
    "        if cell in cache:\n",
    "            return cache[cell]\n",
    "        cache[cell] = (row == 0 and col == 0 or\n",
    "                       self._find_path(matrix, row, col - 1, cache, path) or\n",
    "                       self._find_path(matrix, row - 1, col, cache, path))\n",
    "        if cache[cell]:\n",
    "            path.append(cell)\n",
    "        return cache[cell]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Unit Test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting test_grid_path.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile test_grid_path.py\n",
    "from nose.tools import assert_equal\n",
    "\n",
    "\n",
    "class TestGridPath(object):\n",
    "\n",
    "    def test_grid_path(self):\n",
    "        grid = Grid()\n",
    "        assert_equal(grid.find_path(None), None)\n",
    "        assert_equal(grid.find_path([[]]), None)\n",
    "        max_rows = 8\n",
    "        max_cols = 4\n",
    "        matrix = [[1] * max_cols for _ in range(max_rows)]\n",
    "        matrix[1][1] = 0\n",
    "        matrix[2][2] = 0\n",
    "        matrix[3][0] = 0\n",
    "        matrix[4][2] = 0\n",
    "        matrix[5][3] = 0\n",
    "        matrix[6][1] = 0\n",
    "        matrix[6][3] = 0\n",
    "        matrix[7][1] = 0\n",
    "        result = grid.find_path(matrix)\n",
    "        expected = [(0, 0), (1, 0), (2, 0),\n",
    "                    (2, 1), (3, 1), (4, 1),\n",
    "                    (5, 1), (5, 2), (6, 2), \n",
    "                    (7, 2), (7, 3)]\n",
    "        assert_equal(result, expected)\n",
    "        matrix[7][2] = 0\n",
    "        result = grid.find_path(matrix)\n",
    "        assert_equal(result, None)\n",
    "        print('Success: test_grid_path')\n",
    "\n",
    "\n",
    "def main():\n",
    "    test = TestGridPath()\n",
    "    test.test_grid_path()\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Success: test_grid_path\n"
     ]
    }
   ],
   "source": [
    "%run -i test_grid_path.py"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
